Studies Provider
The Studies Provider is responsible for managing studies (indicators) data for the chart. It handles the creation, updating, and removal of studies (e.g., technical indicators) based on the provided settings and candle data. This provider is essential for dynamically calculating and displaying indicators such as moving averages, Bollinger Bands, MACD, etc.
/// A protocol defining the methods required to manage studies (indicators) for a chart.////// A `StudiesProvider` is responsible for creating, updating, and removing studies (e.g., technical indicators)/// based on the provided settings and candle data. It is typically used in conjunction with a chart to dynamically/// calculate and display indicators such as moving averages, Bollinger Bands, MACD, etc.public protocol StudiesProvider {/// Creates studies (indicators) based on the provided settings and candle data.////// This method calculates the studies (e.g., technical indicators) using the provided `indicators` settings/// and `candles` data. The calculated studies are returned via the `completion` handler.////// - Parameters:/// - indicators: An array of `Indicator` objects defining the studies to be calculated./// - candles: An array of `Candle` objects representing the historical data used for calculations./// - schedule: An optional `Trading.Schedule` object representing the trading schedule. This can be used/// to adjust calculations based on market hours./// - completion: A closure that is called with the calculated studies as an array of `StudyData` objects.func makeStudies(indicators: [Indicator],candles: [Candle],schedule: Trading.Schedule?,completion: @escaping ([StudyData]) -> Void)/// Updates existing studies with new candle data or settings.////// This method recalculates the studies based on the provided `indicators` settings and optionally/// new `candles` data. The updated studies are returned via the `completion` handler.////// - Parameters:/// - indicators: An array of `Indicator` objects defining the studies to be updated./// - newCandles: An optional array of `Candle` objects representing new or updated historical data./// - schedule: An optional `Trading.Schedule` object representing the trading schedule./// - completion: A closure that is called with the updated studies as an array of `StudyData` objects.func updateStudy(indicators: [Indicator],newCandles: [Candle]?,schedule: Trading.Schedule?,completion: @escaping ([StudyData]) -> Void)/// Notifies the provider that studies (indicators) are no longer needed and can be cleaned up.////// This method informs the provider that the studies are no longer required, allowing it to release/// any associated resources or stop ongoing calculations. If specific indicators are provided,/// only those studies will be removed; otherwise, all studies will be cleaned up.////// - Parameters:/// - indicators: An optional array of `Indicator` objects specifying which studies to remove./// If `nil`, all studies will be removed./// - completion: An optional closure that is called when the provider has finished cleaning up.func removeStudies(for indicators: [Indicator]?,completion: (() -> Void)?)}
Default Provider
If you haven’t implemented a custom data provider and are using the DXStudies framework, you can use the default provider included in the framework. This provider is designed to work seamlessly with the DXStudies framework and handles the calculation and management of studies (indicators) for your chart.
import DXChartimport os#if !targetEnvironment(simulator)import dxstudiesfinal class DXStudiesProvider: DXChart.StudiesProvider {private var lastCandleTimeStamp: Int64 = 0private var candles: [DXChart.Candle] = []private var dxStudies: DxStudies<StudiesCandle>?private var calculatorsData: [CalculatorsData] = []private var lastCandleIndex = 0private var isFinished = falsefunc makeStudies(indicators: [DXChart.StudiesSetting],candles: [DXChart.Candle],schedule: Trading.Schedule?,completion: @escaping ([StudyData]) -> Void) {self.isFinished = falseself.candles = candlesself.calculatorsData.removeAll()dxStudies = createCalculator(candles: candles)calculatorsData = indicators.compactMap { indicator -> CalculatorsData? inguard let parameters = indicator.parameters else {return nil}let studiesParams = self.createKotlinStudyParamArray(from: parameters)guard let studyData = self.createStudy(id: indicator.id,studiesParams: studiesParams,candles: candles,schedule: schedule) else {return nil}return CalculatorsData(id: indicator.id, study: studyData)}let studyData = self.calculateStudyData(self.calculatorsData)let swiftStudyData = self.convertToSwiftData(kotlinData: studyData)if !candles.isEmpty {lastCandleTimeStamp = candles.last!.timestamplastCandleIndex = candles.count - 1}isFinished = truecompletion(swiftStudyData)}func updateStudy(indicators: [StudiesSetting],newCandles: [Candle]?,schedule: Trading.Schedule?,completion: @escaping ([StudyData]) -> Void) {guardisFinished,!indicators.isEmpty,candles.count > lastCandleIndex,let newCandle = newCandles?.lastelse { return }let indexIfExist = candles.firstIndex { $0.timestamp == newCandle.timestamp }guard indexIfExist == nil || indexIfExist == lastCandleIndex else { return }let isSameCandle = lastCandleTimeStamp == newCandle.timestampif isSameCandle {candles[lastCandleIndex] = newCandle} else {candles.append(newCandle)}dxStudies?.addCandleItems(items: KotlinArray(size: 1, init: { int inStudiesCandle(candle: newCandle)}))let newLastIndex = self.lastCandleIndex + (isSameCandle ? 0 : 1)lastCandleTimeStamp = newCandle.timestampcalculatorsData.forEach { calcData invar calcStudyData: [KotlinDoubleArray?] = []var updatedStudyData = [StudyDataKotlin]()for i in self.lastCandleIndex...newLastIndex {calcStudyData.append(calcData.study.calculateAt(index: Int32(i)))}let kotlinArray = KotlinArray(size: Int32(calcStudyData.count)) { int incalcStudyData[int.intValue]}updatedStudyData.append(StudyDataKotlin(array: kotlinArray, id: calcData.id))let result = convertToSwiftData(kotlinData: updatedStudyData)completion(result)}lastCandleIndex = newLastIndex}func removeStudies(for indicators: [DXChart.Indicator]?, completion: (() -> Void)?) {candles.removeAll()calculatorsData.removeAll()dxStudies = nil}}private extension DXStudiesProvider {private func createCalculator(candles: [Candle]) -> DxStudies<StudiesCandle> {let array = KotlinArray<StudiesCandle>(size: Int32(candles.count)) { int inlet index = Int(truncating: int)let studiesCandle = StudiesCandle(candle: candles[index])return studiesCandle}return DxStudies(maxElements: Int32.max, candles: array)}private func createStudy(id: String,studiesParams: KotlinArray<StudyParam>,candles: [Candle]? = nil,schedule: Trading.Schedule?) -> Study? {if id == "VWAP", let dataSession = schedule {let sessions = dataSession.days.compactMap(.sessions).flatMap { $0 }let session = KotlinArray(size: Int32(sessions.count)) { int inlet highlights = sessions[Int(truncating: int)]return TradingSessionClass(sessionType: highlights.type.rawValue,from: Double(highlights.startTime.millisecondsSince1970),to: Double(highlights.endTime.millisecondsSince1970)) as TradingSessionData}dxStudies?.setTradingSessions(sessions: session)}let calcStudy = dxStudies!.createStudy(id: id, params: studiesParams)return calcStudy}private func createKotlinStudyParamArray(from parameters: [StudiesSetting.Parameter]) -> KotlinArray<StudyParam> {let studiesParams = parameters.map { item inlet value = getTypedValue(param: item)let studyParameter = StudyParam(key: item.id, value: value!)return studyParameter}let arrayParams = KotlinArray(size: Int32(studiesParams.count), init: { int instudiesParams[Int(truncating: int)]})return arrayParams}private func calculateStudyData(_ studiesCalculator: [CalculatorsData]) -> [StudyDataKotlin] {return studiesCalculator.map { element inlet calcStudyData = element.study.calculateAll()return StudyDataKotlin(array: calcStudyData, id: element.id)}}private func convertToSwiftData(kotlinData: [StudyDataKotlin]) -> [StudyData] {return kotlinData.compactMap { data invar swiftStudyData: [[Double]] = []guard let dataArray = data.array else { return nil }for i in 0..<dataArray.size {let kotlinDoubleArray = dataArray.get(index: i)var swiftDoubleArray: [Double] = []for j in 0..<kotlinDoubleArray!.size {let kotlinDouble = kotlinDoubleArray!.get(index: j)let swiftDouble = Double(kotlinDouble)swiftDoubleArray.append(swiftDouble)}swiftStudyData.append(swiftDoubleArray)}return StudyData(id: data.id, array: swiftStudyData)}}private func getTypedValue(param: StudiesSetting.Parameter) -> Any? {let paramType = param.studyParamTypelet paramValue = param.valueswitch paramType {case .intRange:var value: KotlinIntdo {value = try KotlinInt(int: Int32(paramValue, format: .number))} catch {value = 0}return valuecase .doubleRange:return Double(paramValue)case .stringType:return paramValuecase .boolean:return Bool(paramValue.lowercased())default:return paramValue}}}private class StudiesCandle: CandleDataItem {var isVisible: Bool = truevar open: Doublevar close: Doublevar high: Doublevar low: Doublevar time: Doublevar volume: Double/// Volume-weighted average pricevar vwap: KotlinDouble?var impVolatility: KotlinDouble? = nilinit(candle: Candle) {self.close = candle.closeself.open = candle.openself.high = candle.hiself.low = candle.loself.time = Double(candle.timestamp)self.volume = candle.volumeif let vwap = candle.vwap {self.vwap = KotlinDouble(value: vwap)} else {self.vwap = nil}}}private struct StudyDataKotlin {var array: KotlinArray<KotlinDoubleArray>?var id: String}private struct CalculatorsData {var id: Stringvar study: Study}private class TradingSessionClass: TradingSessionData {var sessionType: Stringvar from: Doublevar high_: KotlinDouble?var low_: KotlinDouble?var to: Doubleinit(sessionType: String,from: Double,high_: KotlinDouble? = nil,low_: KotlinDouble? = nil,to: Double) {self.sessionType = sessionTypeself.from = fromself.high_ = high_self.low_ = low_self.to = to}}#endif